/*
 *  Copyright 2019-2020
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.qp.basics.admin.controller;

import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.qp.basics.admin.config.rsa.RsaProperties;
import com.qp.basics.auth.config.bean.LoginCodeEnum;
import com.qp.basics.auth.config.bean.LoginProperties;
import com.qp.basics.auth.config.bean.SecurityProperties;
import com.qp.basics.auth.service.OnlineUserService;
import com.qp.basics.auth.service.dto.AuthUserDto;
import com.qp.basics.auth.service.dto.JwtUserDto;
import com.qp.basics.common.annotation.rest.AnonymousDeleteMapping;
import com.qp.basics.common.annotation.rest.AnonymousGetMapping;
import com.qp.basics.common.annotation.rest.AnonymousPostMapping;
import com.qp.basics.common.exception.BadRequestException;
import com.qp.basics.common.feign.JwtToken;
import com.qp.basics.common.feign.OAuth2FeignClient;
import com.qp.basics.common.service.dto.UserDto;
import com.qp.basics.common.utils.*;
import com.wf.captcha.base.Captcha;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author
 * @date 2018-11-23
 * 授权、根据token获取用户详细信息
 */
@Slf4j
@RestController
@RequestMapping("/admin/auth")
@RequiredArgsConstructor
@Api(tags = "系统：系统授权接口")
public class AuthorizationController {
    private final SecurityProperties properties;
    private final RedisUtils redisUtils;
    private final OnlineUserService onlineUserService;
    @Resource
    private LoginProperties loginProperties;
    @Autowired
    private OAuth2FeignClient oAuth2FeignClient;
    @Value("${basic.token:Basic eXEtc2Fzcy1hcGk6eXEtc2Fzcy1zZWNyZXQ=}")
    private String basicToken ;

    //@Log("用户登录")
    @ApiOperation("登录授权")
    @AnonymousPostMapping(value = "/login")
    public ResponseEntity<Object> login(@Validated @RequestBody AuthUserDto authUser) throws Exception {
        // 密码解密
        String password = authUser.getPassword();
        try{
            password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword());
        }catch(Exception e){}
        // 查询验证码
        String code = (String) redisUtils.get(authUser.getUuid());
        // 清除验证码
        redisUtils.del(authUser.getUuid());
        if (StringUtils.isBlank(code)) {
            throw new BadRequestException("验证码不存在或已过期");
        }
        if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) {
            throw new BadRequestException("验证码错误");
        }
        ResponseEntity<JwtToken> tokenResponseEntity = oAuth2FeignClient.getToken("password", authUser.getUsername(), password, "admin_type", basicToken);
        if(tokenResponseEntity.getStatusCode()!= HttpStatus.OK){
            throw new BadRequestException("登录授权失败") ;
        }
        JwtToken jwtToken = tokenResponseEntity.getBody();
        log.info("远程调用授权服务器成功,获取的token为{}", JSON.toJSONString(jwtToken,true));
        String token = jwtToken.getAccessToken() ;

        Jwt jwt = JwtHelper.decode(token);
        String jwtJsonStr = jwt.getClaims();
        JSONObject jwtJson = JSON.parseObject(jwtJsonStr);
        String name = jwtJson.getString("user_name");

        JSONArray authoritiesJsonArray = jwtJson.getJSONArray("authorities");
        List<GrantedAuthority> authorities = authoritiesJsonArray!=null? authoritiesJsonArray.stream() // 组装我们的权限数据
                .map(authorityJson->new SimpleGrantedAuthority(authorityJson.toString()))
                .collect(Collectors.toList()):new ArrayList<>();

        User principal = new User(name, "******", authorities);
//        if(principal instanceof UserDetails){
//            System.out.println("xxxxx");
//        }
//        Authentication authentication = new UsernamePasswordAuthenticationToken(principal, token, authorities);
//        SecurityContextHolder.getContext().setAuthentication(authentication);

        JwtUserDto jwtUserDto = null;
        try{
            UserDto userDto = (UserDto) redisUtils.get("user::username:"+name);
            Long userId = userDto.getId();
            List<Long> dataScopes = (List<Long>) redisUtils.get("data::user:"+userId);
            jwtUserDto = new JwtUserDto(userDto, dataScopes, authorities);
        }catch (Exception e){
            throw new BadRequestException("获取用户信息失F败",e) ;
        }

        // 保存在线信息
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        onlineUserService.save(jwtUserDto, token, request);
        // 返回 token 与 用户信息
        JwtUserDto finalJwtUserDto = jwtUserDto;
        Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
            put("token", properties.getTokenStartWith() + token);
            put("user", finalJwtUserDto);
        }};
        if (loginProperties.isSingleLogin()) {
            //踢掉之前已经登录的token
            onlineUserService.checkLoginOnUser(authUser.getUsername(), token);
        }
        return ResponseEntity.ok(authInfo);
    }

    @ApiOperation("获取用户信息")
    @GetMapping(value = "/info")
    public ResponseEntity<Object> getUserInfo() {
        return ResponseEntity.ok(SecurityUtils.getCurrentUser());
    }

    @ApiOperation("获取验证码")
    @AnonymousGetMapping(value = "/code")
    public ResponseEntity<Object> getCode() {
        // 获取运算的结果
        Captcha captcha = loginProperties.getCaptcha();
        String uuid = properties.getCodeKey() + IdUtil.simpleUUID();
        //当验证码类型为 arithmetic时且长度 >= 2 时，captcha.text()的结果有几率为浮点型
        String captchaValue = captcha.text();
        if (captcha.getCharType() - 1 == LoginCodeEnum.arithmetic.ordinal() && captchaValue.contains(".")) {
            captchaValue = captchaValue.split("\\.")[0];
        }
        // 保存
        redisUtils.set(uuid, captchaValue, loginProperties.getLoginCode().getExpiration(), TimeUnit.MINUTES);
        // 验证码信息
        Map<String, Object> imgResult = new HashMap<String, Object>(2) {{
            put("img", captcha.toBase64());
            put("uuid", uuid);
        }};
        return ResponseEntity.ok(imgResult);
    }

    @ApiOperation("退出登录")
    @AnonymousDeleteMapping(value = "/logout")
    public ResponseEntity<Object> logout(HttpServletRequest request) {
        String token = "";
        Enumeration<String> tokens = request.getHeaders(HttpHeaders.AUTHORIZATION);
        if(tokens!=null && tokens.hasMoreElements()){
            token = tokens.nextElement();
        }
        token = token == null ? null : token.replace(properties.getTokenStartWith(),"");
        onlineUserService.logout(token);
        return new ResponseEntity<>(HttpStatus.OK);
    }
}
